package org.eden.heartbeat.api.jmx;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eden.heartbeat.api.Constants;
import org.eden.heartbeat.api.configuration.ApplicationConfiguration;
import org.eden.heartbeat.api.exception.JMXException;

import javax.management.JMX;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;

/**
 * Heartbeat JMX service singleton that handles the JMX resources.
 *
 * @author trsimoes
 * @version $Revision:$
 */
public class HeartbeatJMXService {

    private static Log LOG = LogFactory.getLog(HeartbeatJMXService.class);

    private static HeartbeatJMXService ourInstance = new HeartbeatJMXService();

    private JMXConnector jmxConnector = null;

    private MBeanServerConnection mbeanServerConnection = null;

    private HeartbeatCoreMBean heartbeatCoreProxy = null;

    private HeartbeatStatisticsMBean heartbeatStatisticsMBean = null;

    public static HeartbeatJMXService getInstance() {
        return ourInstance;
    }

    private HeartbeatJMXService() {
    }

    /**
     * Starts the Heartbeat JMX service.
     *
     * @throws org.eden.heartbeat.core.exception.JMXException thrown when the JMX service cannot be started.
     */
    public void start() throws JMXException {
        try {
            JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:" + ApplicationConfiguration.jmxPort +
                    "/jmxrmi");
            this.jmxConnector = JMXConnectorFactory.connect(url, null);
            this.mbeanServerConnection = jmxConnector.getMBeanServerConnection();

        } catch (Throwable e) {
            throw new JMXException(e);
        }
    }

    /**
     * Shuts down the Heartbeat JMX service.
     *
     * @throws org.eden.heartbeat.core.exception.JMXException thrown when the JMX service cannot be started.
     */
    public void close() throws JMXException {
        if (this.jmxConnector != null) {
            try {
                this.jmxConnector.close();
            } catch (IOException e) {
                if (LOG.isErrorEnabled()) {
                    LOG.error("Error closing the JMX connector", e);
                }
            }
        }
    }

    /**
     * Gets the HeartbeatCoreMBean.
     *
     * @return the heartbeatCoreProxy.
     */
    public HeartbeatCoreMBean getHeartbeatCoreProxy() throws JMXException {
        if (this.heartbeatCoreProxy == null) {
            this.heartbeatCoreProxy = getProxy(HeartbeatCoreMBean.class, Constants.HEARTBEAT_CORE_MBEAN_OBJECT_NAME);
        }
        return this.heartbeatCoreProxy;
    }

    /**
     * Gets the HeartbeatStatisticsMBean.
     *
     * @return the heartbeatStatisticsMBean.
     */
    public HeartbeatStatisticsMBean getHeartbeatStatisticsProxy() throws JMXException {
        if (this.heartbeatStatisticsMBean == null) {
            this.heartbeatStatisticsMBean = getProxy(HeartbeatStatisticsMBean.class, Constants.
                    HEARTBEAT_STATISTICS_MBEAN_OBJECT_NAME);
        }
        return this.heartbeatStatisticsMBean;
    }

    /**
     * Gets a JMX proxy by its type and unique name.
     *
     * @param proxyClass the proxy class type
     * @param proxyName the name that the proxy was registered with
     * @param <T> the class type
     * @return the instance of the proxy
     * @throws org.eden.heartbeat.core.exception.JMXException thrown when the proxy cannot be returned
     */
    private <T> T getProxy(final Class<T> proxyClass, final String proxyName) throws JMXException {
        try {
            ObjectName mbeanName = new ObjectName(proxyName);
            T proxy = proxyClass.cast(JMX.newMBeanProxy(this.mbeanServerConnection, mbeanName, proxyClass, true));
            if (LOG.isInfoEnabled()) {
                LOG.info("Connected to: " + proxyClass.getSimpleName() + ".");
            }
            return proxy;
        } catch (MalformedObjectNameException e) {
            throw new JMXException(e);
        }
    }
}
